import base64
import hashlib
import json
import random
import time

import jwt
import requests
from django.core.cache import caches
from django.db import transaction
from django.http import JsonResponse
from django.views import View

from dashopt import settings
from users.tasks import async_send_active_email, async_send_message
from utils.logging_dec import logging_check
from utils.smsapi import send_message
from .models import UserProfile, Address, WeiboProfile


# Create your views here.

def register_view(request):
    """
    注册功能逻辑
    1.获取请求体数据
    2.数据合法性校验
    3.确认用户名是否被占用
        3.1 被占用：直接返回
        3.2 未被占用：存入数据表
            签发token，返回响应[接口文档]
    """
    # 1.获取请求体数据
    # post属性：获取form表单提交的请求体数据
    # body属性：获取json形式提交的请求体数据
    data = json.loads(request.body)
    username = data.get('uname')
    password = data.get('password')
    email = data.get('email')
    phone = data.get('phone')
    verify = data.get('verify')

    # 短信验证码的校验
    key2 = f"sms_ex_{phone}"
    redis_code = caches["sms"].get(key2)
    if not redis_code:
        return JsonResponse({"code": 10110, "error": "验证码已过期请重新获取"})
    if str(redis_code) != verify:
        return JsonResponse({"code": 10111, "error": "验证码错误,请重新输入!"})

    # 2.数据合法性校验
    if len(username) < 6 or len(username) > 11:
        return JsonResponse({'code': 10100, 'error': '用户名需要在6-11位'})
    if len(password) < 6 or len(password) > 12:
        return JsonResponse({'code': 10101, 'error': '密码需要在6-12位'})
    if len(phone) != 11:
        return JsonResponse({'code': 10102, 'error': '手机号码不合法!'})

    # 确认用户名是否被占用
    user_query = UserProfile.objects.filter(username=username)
    if user_query:
        return JsonResponse({'code': 10103, 'error': '用户名重复'})
    # 存入数据表
    user = UserProfile.objects.create(
        username=username,
        password=md5_string(password),
        email=email,
        phone=phone,
    )
    verify_url = get_verify_url(username)
    async_send_active_email.delay(email, username, verify_url)

    # 生成 token
    token = make_token(username)

    # 清除验证码，释放内存
    caches["sms"].delete(key2)

    result = {"code": 200,
              "username": username,
              "token": token,
              "carts_count": 0}
    return JsonResponse(result)


# FBV: Function Base View 函数视图
# CBV: Class Base View 类视图
class AddressView(View):

    @logging_check
    def get(self, request, username):
        """
        查询收货地址视图逻辑
        1.查询该用户所有的收货地址
        2.组装数据返回响应
        UserProfile.objects.get(request.my_user)
        """
        addresslist = []
        addr_query = Address.objects.filter(
            user_profile=request.my_user,
            is_delete=False,
        )
        for addr in addr_query:
            addr_dict = {
                'id': addr.id,
                'address': addr.address,
                'receiver': addr.receiver,
                'receiver_mobile': addr.receiver_mobile,
                'tag': addr.tag,
                'postcode': addr.postcode,
                'is_default': addr.is_default,
            }
            addresslist.append(addr_dict)
        result = {
            'code': 200,
            'addresslist': addresslist
        }
        return JsonResponse(result)

    @logging_check
    def post(self, request, username):
        """
        添加收货地址视图逻辑
        1.获取请求体数据
        2.存入地址表
            第一个地址：添加并设置为默认
            非第一个地址：添加地址
        """

        data = request.my_data
        receiver = data.get('receiver')
        receiver_phone = data.get('receiver_phone')
        address = data.get('address')
        postcode = data.get('postcode')
        tag = data.get('tag')
        user = request.my_user
        # 查询该用户是否有收货地址
        addr_query = Address.objects.filter(user_profile=user, is_delete=False)
        is_default = False if addr_query else True
        Address.objects.create(
            user_profile=user,
            receiver=receiver,
            address=address,
            postcode=postcode,
            receiver_mobile=receiver_phone,
            tag=tag,
            is_default=is_default,
        )
        return JsonResponse({'code': 200, 'data': "新增地址成功!"})

    @logging_check
    def put(self, request, username, id):
        """
        修改地址视图逻辑
        1.获取请求体的数据
        2.一查二改三保存
        3.返回响应
        """
        user = request.my_user
        try:
            addr = Address.objects.get(
                id=id, user_profile=user,
                is_delete=False)
        except Exception as e:
            return JsonResponse({
                'code': 10106,
                'error': "违法请求！"
            })
        data = request.my_data
        # 1.获取请求体数据
        receiver = data.get('receiver')
        receiver_mobile = data.get('receiver_mobile')
        address = data.get('address')
        tag = data.get('tag')
        # 2.二改三保存，返回响应
        addr.receiver = receiver
        addr.receiver_mobile = receiver_mobile
        addr.address = address
        addr.tag = tag
        addr.save()
        return JsonResponse({"code": 200, "data": "地址修改成功！"})

    @logging_check
    def delete(self, request, username, id):
        """
        删除地址视图逻辑
        伪删除：本质上仍然是更新，
        """
        user = request.my_user
        try:
            addr = Address.objects.get(
                id=id, user_profile=user,
                is_delete=False)
        except Exception as e:
            return JsonResponse({
                'code': 10107,
                'error': "违法请求！"
            })
        addr.is_delete = True
        addr.save()
        return JsonResponse({"code": 200, "data": "删除地址成功！"})


class DefaultAddressView(View):
    @logging_check
    def post(self, request, username):
        """
        设置默认地址的视图逻辑
        1.获取请求体的数据[id]
        2.把原来的默认地址取消默认
        3.把现在的地址设置为默认
        4.返回响应
        """
        user = request.my_user
        id = request.my_data.get('id')
        # 开启事务
        with transaction.atomic():
            # 创建存储点
            sid = transaction.savepoint()
            try:
                old_query = \
                    Address.objects.filter(
                        is_default=True,
                        user_profile=user,
                        is_delete=False)
                if old_query:
                    old_addr = old_query[0]
                    old_addr.is_default = False
                    old_addr.save()
                new_query = Address.objects.filter(
                    id=id,
                    user_profile=user,
                    is_delete=False
                )
                new_addr = new_query[0]
                new_addr.is_default = True
                new_addr.save()
            except Exception as e:
                # 回滚 + 返回
                transaction.savepoint_rollback(sid)
                return JsonResponse({"code": 10107, "data": "设置默认地址失败！"})
            # 提交事务
            transaction.savepoint_commit(sid)
        return JsonResponse({"code": 200, "data": "设置默认地址成功"})


class WeiboCodeView(View):
    def get(self, request):
        """
        获取授权码code视图逻辑
        API文档：获取微博API文档
        响应:{"code":200,"oauth_url":"url"}
        """
        oauth_url = f"https://api.weibo.com" \
                    f"/oauth2/authorize?client_id" \
                    f"={settings.CLIENT_ID}" \
                    f"&redirect_uri={settings.REDIRECT_URI}&response_type=code"
        result = {
            "code": 200,
            "oauth_url": oauth_url
        }
        return JsonResponse(result)


class WeiboTokenView(View):
    def get(self, request):
        """
        获取访问令牌access_token视图逻辑
        1.获取访问授权码code
        2.利用code获取访问令牌access_token
        """
        code = request.GET.get("code")
        # 访问令牌微博接口文档
        url = "https://api.weibo.com/oauth2/access_token"
        data = {
            "client_id": settings.CLIENT_ID,
            "client_secret": settings.CLIENT_SECRET,
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": settings.REDIRECT_URI
        }
        resp = requests.post(url=url, data=data).json()
        wuid = resp.get("uid")
        access_token = resp.get("access_token")
        """
        情况1：第一次扫码登录：存入微博表并返回绑定注册页面[201]
        情况2：非第一次扫码登录：1.在绑定注册页关闭页面 返回绑定注册页面[201]
              2.已经和正式账号绑定过 返回已经登录的首页[200]
              200响应:{"code":200;"token":token;"username":username}
              201响应:{"code":201;"uid":wuid}
        """
        try:
            weibo_user = WeiboProfile.objects.get(wuid=wuid)
        except Exception as e:
            WeiboProfile.objects.create(wuid=wuid, access_token=access_token)
            return JsonResponse({"code": 201, "uid": wuid})
        user = weibo_user.user_profile
        username = user.username
        token = make_token(username)
        if not user:
            return JsonResponse({"code": 201, "uid": wuid})
        return JsonResponse({"code": 200, "token": token, "username": user.username})

    def post(self, request):
        """
        第三方微博登录,无账号逻辑
        1.获取请求体数据
        2.合法性校验
        3.校验用户名是否被占用
        4.发送激活邮件
        5.绑定并返回响应
        """
        data = json.loads(request.body)
        username = data.get("username")
        password = data.get("password")
        email = data.get("email")
        phone = data.get("phone")
        wuid = data.get("uid")
        # 2.数据合法性校验
        if len(username) < 6 or len(username) > 11:
            return JsonResponse({'code': 10113, 'error': '用户名需要在6-11位'})
        if len(password) < 6 or len(password) > 12:
            return JsonResponse({'code': 10114, 'error': '密码需要在6-12位'})
        if len(phone) != 11:
            return JsonResponse({'code': 10115, 'error': '手机号码不合法!'})

        # 确认用户名是否被占用
        user_query = UserProfile.objects.filter(username=username)
        if user_query:
            return JsonResponse({'code': 10116, 'error': '用户名重复'})

        # 开启事务
        with transaction.atomic():
            sid = transaction.savepoint()
            # 存入数据表
            try:
                user = UserProfile.objects.create(
                    username=username,
                    password=md5_string(password),
                    email=email,
                    phone=phone,
                )
                # 绑定
                wuser = WeiboProfile.objects.get(wuid=wuid)
                wuser.user_profile = user
                wuser.save()
            except Exception as e:
                transaction.savepoint_rollback(sid)
                return JsonResponse({"code": 10117, "error": "服务器繁忙请稍后再试"})
            # 提交事务
            transaction.savepoint_commit(sid)
        # 发送激活邮件
        verify_url = get_verify_url(username)
        async_send_active_email.delay(email, username, verify_url)
        token = make_token(username)
        result = {"code": 200,
                  "username": username,
                  "token": token,
                  "carts_count": 0}
        return JsonResponse(result)


class BindUserView(View):
    def post(self, request):
        """
        已有账号，请绑定视图逻辑
        1.获取请求体数据
        2.校验用户名和密码
        3.绑定并且返回响应
        """
        data = json.loads(request.body)
        username = data.get("username")
        password = data.get("password")
        wuid = data.get("uid")

        try:
            user = UserProfile.objects.get(username=username,
                                           password=md5_string(password))
        except Exception as e:
            return JsonResponse({"code": 10111, "error": "用户名或密码错误"})
        # 将微博用户和正式用户进行绑定
        try:
            wuser = WeiboProfile.objects.get(wuid=wuid)
        except Exception as e:
            return JsonResponse({"code": 10112, "error": "微博服务器繁忙请稍后再试!"})
        wuser.user_profile = user
        wuser.save()
        token = make_token(username)
        return JsonResponse({"code": 200, "token": token, "username": username})


def login_view(request):
    """
    登录功能逻辑
        1.获取请求体数据
        2.校验用户名和密码是否正确
            2.1 错误：直接返回
            2.2 正确：生成token返回json
    """
    # 获取请求体数据
    data = json.loads(request.body)
    username = data.get('username')
    password = data.get('password')
    user_query = UserProfile.objects.filter(
        username=username,
        password=md5_string(password))
    if not user_query:
        return JsonResponse({'code': 10104,
                             'error': '用户名或密码不正确'})
    result = {"code": 200,
              "username": username,
              "token": make_token(username),
              "carts_count": 0}
    return JsonResponse(result)


def active_view(request):
    """
    邮件激活视图逻辑
    1.获取查询参数：code
    2.校验 用户名和随机数
    3.激活用户：一查二改三保存
    """
    code = request.GET.get('code')
    code_str = base64.b64decode(code.encode()).decode()
    code_num, username = code_str.split('_')
    key = f"active_{username}"
    redis_num = caches["default"].get(key)
    if str(redis_num) != code_num:
        return JsonResponse({"code": 10108, "error": "激活失败！"})
    # 激活用户
    try:
        user = UserProfile.objects.get(username=username)
    except Exception as e:
        return JsonResponse({"code": 10108, "error": "服务器繁忙请稍后再试！"})
    user.is_active = True
    user.save()
    return JsonResponse({"code": 200, "data": "激活成功！"})


def sms_view(request):
    """
    发送短信验证码视图逻辑
    1.获取请求体数据[手机号]
    2.发短信[调接口]
    3.返回响应
    """
    mobile = json.loads(request.body).get("phone")
    # Redis:{"sms_18892689998":"code"}
    # 判断一分钟之内是否发过
    key1 = f"sms_{mobile}"
    r = caches["sms"].get(key1)
    if r:
        return JsonResponse({"code": 10109, "error": "短信发送过于频繁!"})
    code = random.randint(1000, 9999)
    datas = (code, 5)
    # celery异步发送短信验证码
    async_send_message.delay("1", mobile, datas)
    # 存放Redis数据库
    # 60s用于控制短信发送频率
    caches["sms"].set(key1, code, 60)
    # 300s用于短信验证码的校验
    key2 = f"sms_ex_{mobile}"
    caches["sms"].set(key2, code, 600)
    return JsonResponse({"code": 200, "data": "验证码发送成功!"})


def md5_string(password):
    """
    功能函数 ：md5加密
    """
    m = hashlib.md5()
    m.update(password.encode())
    return m.hexdigest()


def make_token(username, expire=86400):
    payload = {'exp': time.time() + expire,
               'username': username}
    key = settings.JWT_TOKEN_KEY
    return jwt.encode(payload, key)


def get_verify_url(username):
    """
    功能函数：生成邮件激活链接
    """
    # 发送激活邮件
    rand_code = random.randint(1000, 9999)
    # 针对随机数加用户名编码
    code_str = f"{rand_code}_{username}"
    code = base64.b64encode(code_str.encode()).decode()
    verify_url = f"http://127.0.0.1:7000/dadashop/templates/active.html?code={code}"
    # 将随机数存入Redis
    key = f"active_{username}"
    caches["default"].set(key, rand_code, 86400 * 3)
    return verify_url
